Logo
Published on

9.!=和is not运算符有什么区别?

Authors
  • avatar
    Name
    xiaobai
    Twitter

1. Python中!=和is not运算符有什么区别?

请详细说明它们比较的对象、应用场景以及在实际使用中需要注意的陷阱

2.核心区别

!=is not 是Python中用于判断两个对象是否"不相等"的两种不同运算符,但它们关注的"不相等"维度不同:

  1. != 运算符
    • 比较对象:比较两个对象的**值(value)**是否不相等。
    • 判断依据:如果两个对象的内容或值不同,则返回 True;否则返回 False
    • 等价于not (a == b)
  2. is not 运算符
    • 比较对象:比较两个对象的**身份(identity)**是否不相等,即它们在内存中的存储地址是否不同。
    • 判断依据:如果两个变量引用的是内存中不同的对象(即使它们的值可能相同),则返回 True;否则返回 False
    • 等价于not (a is b)

简而言之,!= 关注的是"内容是否不同",而 is not 关注的是"是不是同一个对象"。

3. != 运算符

!= 运算符用于判断两个对象的值是否不相等。它会调用对象的 __ne__ 方法(如果定义了),或者 __eq__ 方法的否定形式。

# 定义两个列表,它们的值相同但不是同一个对象
list_a = [1, 2, 3]
list_b = [1, 2, 3]
# 定义两个整数,它们的值不同
int_c = 5
int_d = 6

# 比较 list_a 和 list_b 的值是否不相等
# 预期输出:False,因为它们的值是相等的
print(f"list_a != list_b: {list_a != list_b}")

# 比较 list_a 和 list_b 是否是同一个对象
# 预期输出:False,因为它们在内存中是两个不同的列表对象
print(f"list_a is list_b: {list_a is list_b}")

# 比较 int_c 和 int_d 的值是否不相等
# 预期输出:True,因为56的值不相等
print(f"int_c != int_d: {int_c != int_d}")

# 比较 int_c 和 int_d 是否是同一个对象
# 预期输出:False,因为它们是不同的整数对象
print(f"int_c is int_d: {int_c is int_d}")

# 定义一个列表和一个元组,它们的值看起来相似但类型不同
mixed_e = [1, 2, 3]
mixed_f = (1, 2, 3)

# 比较 mixed_e 和 mixed_f 的值是否不相等
# 预期输出:True,因为列表和元组是不同的类型,即使内容相似,值比较也可能不同
print(f"mixed_e != mixed_f: {mixed_e != mixed_f}")

4. is not 运算符

is not 运算符用于判断两个变量是否引用了内存中不同的对象。它直接比较两个对象的内存地址(通过 id() 函数获取)。

# 定义两个列表,它们的值相同但不是同一个对象
obj_x = [10, 20]
obj_y = [10, 20]

# 比较 obj_x 和 obj_y 是否不是同一个对象
# 预期输出:True,因为它们是两个独立的列表对象,内存地址不同
print(f"obj_x is not obj_y: {obj_x is not obj_y}")

# 定义一个列表,并让另一个变量引用它
obj_p = [30, 40]
obj_q = obj_p # obj_q 引用了 obj_p 所指向的同一个对象

# 比较 obj_p 和 obj_q 是否不是同一个对象
# 预期输出:False,因为它们引用的是内存中的同一个列表对象
print(f"obj_p is not obj_q: {obj_p is not obj_q}")

# 比较 obj_p 和 obj_q 的值是否不相等
# 预期输出:False,因为它们引用的是同一个对象,值当然相等
print(f"obj_p != obj_q: {obj_p != obj_q}")

5. is not None 的特殊用法

is not None 是判断一个变量是否为 None推荐方式。 这是因为 None 是一个单例对象(Singleton),在整个Python解释器中只有一个 None 实例。 因此,比较一个变量是否为 None 应该使用身份比较 isis not,而不是值比较 ==!=

# 定义一个变量,初始值为 None
my_variable = None

# 检查 my_variable 是否不是 None
# 预期输出:Variable is None,因为 my_variable 当前是 None
if my_variable is not None:
    # 如果 my_variable 不是 None,则打印此消息
    print("Variable is not None")
else:
    # 如果 my_variable 是 None,则打印此消息
    print("Variable is None")

# 重新赋值 my_variable 为一个字符串
my_variable = "Hello"

# 再次检查 my_variable 是否不是 None
# 预期输出:Variable is not None,因为 my_variable 现在是一个字符串
if my_variable is not None:
    # 如果 my_variable 不是 None,则打印此消息
    print("Variable is not None")
else:
    # 如果 my_variable 是 None,则打印此消息
    print("Variable is None")

# 错误示范:使用 != None 进行比较
# 尽管在大多数情况下结果相同,但这不是推荐的做法,因为某些自定义对象可能重载 != 运算符
# 预期输出:Variable is not None (通常情况下)
if my_variable != None:
    # 如果 my_variable 不等于 None,则打印此消息
    print("Variable is not None (using != None)")

6. 基础数据类型缓存陷阱

Python为了优化性能,会对某些**不可变(immutable)**的基础数据类型进行缓存,例如:

  • 短字符串:某些短的、不包含特殊字符的字符串。

这意味着在这些缓存范围内,即使你创建了两个看起来独立的相同值的对象,Python解释器也可能让它们引用内存中的同一个对象。这会导致 is 运算符返回 True,而 is not 运算符返回 False,这可能与初学者的直觉不符。

# 定义两个短字符串,它们可能被缓存
str1_cached = "hello"
str2_cached = "hello"

# 比较 str1_cached 和 str2_cached 是否是同一个对象
# 预期输出:True,因为短字符串通常会被缓存,它们引用的是同一个对象
print(f"str1_cached is str2_cached: {str1_cached is str2_cached}")
# 比较 str1_cached 和 str2_cached 是否不是同一个对象
# 预期输出:False
print(f"str1_cached is not str2_cached: {str1_cached is not str2_cached}")

# 定义两个长字符串或动态生成的字符串,通常不会被缓存
str1_uncached = "hello"*1000000
str2_uncached = "hello"*1000000

# 比较 str1_uncached 和 str2_uncached 是否是同一个对象
# 预期输出:False,因为长字符串通常不会被缓存,它们是两个不同的对象
print(f"str1_uncached is str2_uncached: {str1_uncached is str2_uncached}")
# 比较 str1_uncached 和 str2_uncached 是否不是同一个对象
# 预期输出:True
print(f"str1_uncached is not str2_uncached: {str1_uncached is not str2_uncached}")

7. ==、is与 id() 函数的讲解

  • == 运算符:用于判断两个对象的值是否相等(即内容是否一致)。它本质上会调用对象的 __eq__ 方法,比如 a == b 实际为 a.__eq__(b)。即使是两个不同的对象,只要内容一样,== 也会返回True。
  • is 运算符:用于判断两个变量是否引用自内存中的同一个对象,即身份(identity)是否一致,本质上就是 id(a) == id(b)is 比较的是对象的内存地址,不关心值是否相等。
  • id() 函数:返回对象的“身份标识”(即内存地址)。如果两个对象 ab 满足 id(a) == id(b),那么 a is b 必然为True,说明它们实际上指向同一个对象。
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)         # True  (值相等)
print(a is b)         # False (不是同一个对象)
print(a is c)         # True  (是同一个对象)
print(id(a), id(b))   # 两个不同的内存地址
print(id(a), id(c))   # 相同的内存地址

使用建议:

  • 判断"内容相同"时请用==,判断"是否同一个对象"时请用is
  • 判断变量是否为None,应使用is None(而非== None)。

8.参考答案

8.1 开场白(15秒)

"!=is not是Python中用于判断两个对象是否'不相等'的两种不同运算符,但它们关注的'不相等'维度完全不同。"

8.2 核心区别(45秒)

"两者的根本区别在于比较的对象不同:

!=运算符

  • 比较两个对象的**值(value)**是否不相等
  • 关注的是内容是否不同
  • 等价于not (a == b)
  • 会调用对象的__ne__方法或__eq__方法的否定形式

is not运算符

  • 比较两个对象的**身份(identity)**是否不相等
  • 关注的是内存地址是否不同
  • 等价于not (a is b)
  • 直接比较对象的内存地址

简单来说!=问的是'内容是否不同',is not问的是'是不是同一个对象'"

8.3 实际例子(30秒)

"举个具体例子:

  • 两个列表[1,2,3][1,2,3]
    • !=返回False,因为值相同
    • is not返回True,因为是不同的对象
  • 两个变量指向同一个对象:
    • !=返回False,因为值相同
    • is not返回False,因为是同一个对象"

8.4 特殊用法(30秒)

"is not None是推荐用法

  • 判断变量是否为None时,应该用is not None
  • 因为None是单例对象,整个Python解释器中只有一个None实例
  • != None虽然结果相同,但不是推荐做法

基础数据类型缓存陷阱

  • Python会缓存某些不可变对象,如短字符串
  • 这可能导致is比较的结果与直觉不符
  • 所以通常只在比较NoneTrueFalse等单例对象时使用is"

8.5 使用建议(20秒)

  • 判断内容是否相同时用==!=
  • 判断是否为同一个对象时用isis not
  • 判断是否为None时用is Noneis not None
  • 其他情况优先使用值比较,避免身份比较的陷阱"

8.6 结尾(10秒)

"总的来说,!=关注内容,is not关注身份,选择哪个取决于你的比较目的。"

8.7 回答技巧提示:

  1. 控制时间:总时长控制在2-3分钟
  2. 突出对比:重点强调值比较vs身份比较的区别
  3. 举例说明:用简单例子说明两种比较的不同结果
  4. 准备深入:如果面试官追问,可以解释id()函数和内存地址的概念
  5. 结合实际:可以提到自己在开发中如何选择使用这两种运算符